using System;
using System.Collections.Generic;

namespace BizElements.Core
{
    /// <summary>Creates instances of IEntityDAO objects that best suite given entity objects.</summary>
    /// <remarks><para>DAO factory contains a collection of default and custom creation strategies for specific entity classes.
    /// Contains default and custom strategies for DAOs. Once a strategy is registered for a class
	/// it is automatically used for all inherited classes, too. The behavior may be overrided by registering
    /// another strategy for inherited class.</para>
    /// <para>Some DAO strategies may work for all entities, and some support only specific entity classes. 
    /// The factory's job is to lookup the best strategy in the collection which will create a DAO that best suites a given entity.</para>
    /// </remarks>
	public static class EntityDaoFactory
    {
        #region Configuration.

        static Dictionary<string, IEntityDaoCreationStrategy> strategies = new Dictionary<string, IEntityDaoCreationStrategy>();
		static IEntityDaoCreationStrategy defaultStrategy;

		static EntityDaoFactory()
		{
			defaultStrategy = new DefaultEntityDaoCreationStrategy();
		}

        #endregion

        #region RegisterStrategy.

        /// <summary>
        /// Registers custom DAO creation strategy for the given entity class and all inheritted classes.
        /// </summary>
        /// <param name="entityClass">Entity class for which DAOs are created using given strategy.
        /// If null then current custom strategy for the specified type will be unregistered.</param>
        /// <param name="strategy">Algorithm used to create DAOs for the specified class.</param>
        public static void RegisterStrategy(Type entityClass, IEntityDaoCreationStrategy strategy)
        {
            if (strategy == null)
                strategies.Remove(entityClass.FullName);
            else
                strategies[entityClass.FullName] = strategy;
        }

        #endregion

        /// <summary>Gets the most optimal DAO that will work the given entity.</summary>
		/// <param name="entity">Entity with wich the returned DAO works with.</param>
		/// <param name="conn">Connection context DAO will use. If null, default DAO settings are used.</param>
		/// <returns>IEntityDAO implementation.</returns>
		/// <remarks><p>Method will try to find the most optimal DAO creation strategy that will create the
		/// most optimal DAO for the given entity.</p>
		/// <p>Current DAO factory is passed on to the created DAO.</p></remarks>
		public static IEntityDAO GetEntityDAO(IEntity entity, IConnectionProvider conn)
		{
            // Optimization if no custom strategies are registered.
            if (strategies.Count == 0)
                return defaultStrategy.GetEntityDAO(entity, conn);

			IEntityDaoCreationStrategy strategy = GetStrategy(entity);
			IEntityDAO dao = strategy.GetEntityDAO(entity, conn);
			return dao;
		}

        #region GetStrategy/FindBestStrategy.

        /// <summary>Gets DAO creation strategy for the given entity.</summary>
        /// <param name="entity">Entity for which the best registered DAO creation strategy is searched.</param>
        /// <returns>Most optimal DAO strategy for the given entity. <see cref="DefaultEntityDaoCreationStrategy"/>
        /// if no custom algorithm has been registered for the entity class or one of its base classes.</returns>
        private static IEntityDaoCreationStrategy GetStrategy(IEntity entity)
        {
            Type entityClass = entity.GetType();
            IEntityDaoCreationStrategy strategy = FindBestStrategy(entityClass);
            return (strategy != null) ? strategy : defaultStrategy;
        }

		private static IEntityDaoCreationStrategy FindBestStrategy(Type entityClass)
		{			
			// Try to find a non-default strategy for the given entity or for one of the classes it is derived from.
			// Search is stopped when algorithm reaches one of the two built-in generic entity classes
            // (EntityModelBase, ActiveRecordBase) for which the DefaultEntityDaoCreationStrategy is guaranteed to work fine.

			IEntityDaoCreationStrategy bestStrategy = strategies[entityClass.FullName] as IEntityDaoCreationStrategy;
			while (bestStrategy == null && !IsBuiltInGenericEntityOrObject(entityClass))
			{
				// Recursively climb the object hierarchy from the given class up to one of the framework's 
				// built-in generic classes. Not required to climb to System.Object if entity is derived from
				// one of the built-in types.
				entityClass = entityClass.BaseType;
				bestStrategy = strategies[entityClass.FullName] as IEntityDaoCreationStrategy;
			}

			return bestStrategy;
		}		

		static readonly Type entityModelBaseClass = typeof(EntityModelBase);
        static readonly Type entityActiveRecordBaseClass = typeof(ActiveRecordBase);
		static readonly Type objectClass = typeof(object);

		private static bool IsBuiltInGenericEntityOrObject(Type entityClass)
		{
			// entityClass may be of type Object if built-in classes are not found in the hierarchy.
			// This may occur if some custom IEntity implementation is used as a base class for entities.
			bool isBuiltIn = (entityClass == entityModelBaseClass  ||  entityClass == entityActiveRecordBaseClass  ||  entityClass == objectClass);
			return isBuiltIn;
        }

        #endregion
    }
}
